package com.polaris.live.common.util

import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.KType
import kotlin.reflect.full.isSubtypeOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.jvmErasure
import kotlin.reflect.typeOf

/**
 * ReflexUtils
 *
 * @author Created by lucas on 2023/10/19 10:56
 */
object ReflexUtils {

    private val TYPE_NUMBER = typeOf<Number?>()
    private val TYPE_CHAR = typeOf<Char?>()
    private val TYPE_BYTE = typeOf<Byte?>()
    private val TYPE_SHORT = typeOf<Short?>()
    private val TYPE_INT = typeOf<Int?>()
    private val TYPE_LONG = typeOf<Long?>()
    private val TYPE_FLOAT = typeOf<Float?>()
    private val TYPE_DOUBLE = typeOf<Double?>()

    /**
     * 将 Map 转换回为对象
     */
    inline fun <reified T : Any> fromMap(map: Map<String, Any>): T? {
        return fromMap(map, T::class)
    }

    /**
     * 将 Map 转换回为对象
     */
    fun <T : Any> fromMap(map: Map<String, Any>, kClass: KClass<T>): T? {
        val constructor = kClass.primaryConstructor ?: return null

        val parameters = constructor.parameters.associateWith { parameter ->
            map[parameter.name]?.let { mapToValue(it, parameter.type) }
        }

        return constructor.callBy(parameters)
    }

    private fun mapToValue(value: Any, kType: KType): Any? {
        val kClass = kType.jvmErasure

        when (value) {
            is Map<*, *> -> {
                if (kClass.isData) {
                    return fromMap(value as Map<String, Any>, kClass)
                } else if (kClass.java.isAssignableFrom(Map::class.java)) {
                    val childKType = kType.arguments.getOrNull(1)?.type
                    if (childKType != null) {
                        return value.entries.associateNotNullStrict { (k, v) ->
                            k to v?.let { mapToValue(it, childKType) }
                        }
                    }
                }
            }
            is Array<*> -> {
                if (kClass.java.isAssignableFrom(Array::class.java)) {
                    val childKType = kType.arguments.firstOrNull()?.type
                    if (childKType != null) {
                        return value.mapNotNull { v -> v?.let { mapToValue(it, childKType) } }
                            .toTypedArray()
                    }
                }
            }
            is Collection<*> -> {
                val childKType = kType.arguments.firstOrNull()?.type
                if (childKType != null) {
                    return if (value is Set<*>) {
                        HashSet<Any>().apply {
                            addAll(value.mapNotNull { obj ->
                                obj?.let { mapToValue(it, childKType) }
                            })
                        }
                    } else {
                        value.mapNotNull { obj ->
                            obj?.let { mapToValue(it, childKType) }
                        }
                    }
                }
            }
        }

        return convertNumber(value, kType) ?: value
    }

    /**
     * 将对象转换为指定类型，如果转换失败则返回 null
     */
    fun convertNumber(value: Any, kType: KType): Any? {
        return if (kType.isSubtypeOf(TYPE_NUMBER)) {
            when (value) {
                is Number -> {
                    toNumber(value, kType)
                }
                is String -> {
                    toNumber(value, kType)
                }
                else -> {
                    null
                }
            }
        } else {
            null
        }
    }

    private fun toNumber(value: Number, kType: KType): Any? {
        return when {
            kType.isSubtypeOf(TYPE_CHAR) -> {
                value.toInt().toChar()
            }
            kType.isSubtypeOf(TYPE_BYTE) -> {
                value.toByte()
            }
            kType.isSubtypeOf(TYPE_SHORT) -> {
                value.toShort()
            }
            kType.isSubtypeOf(TYPE_INT) -> {
                value.toInt()
            }
            kType.isSubtypeOf(TYPE_LONG) -> {
                value.toLong()
            }
            kType.isSubtypeOf(TYPE_FLOAT) -> {
                value.toFloat()
            }
            kType.isSubtypeOf(TYPE_DOUBLE) -> {
                value.toDouble()
            }
            else -> null
        }
    }

    private fun toNumber(value: String, kType: KType): Any? {
        return when {
            kType.isSubtypeOf(TYPE_CHAR) -> {
                value.toInt().toChar()
            }
            kType.isSubtypeOf(TYPE_BYTE) -> {
                value.toByte()
            }
            kType.isSubtypeOf(TYPE_SHORT) -> {
                value.toShort()
            }
            kType.isSubtypeOf(TYPE_INT) -> {
                value.toInt()
            }
            kType.isSubtypeOf(TYPE_LONG) -> {
                value.toLong()
            }
            kType.isSubtypeOf(TYPE_FLOAT) -> {
                value.toFloat()
            }
            kType.isSubtypeOf(TYPE_DOUBLE) -> {
                value.toDouble()
            }
            else -> null
        }
    }

    /**
     * 将对象转换为 Map
     */
    fun <T : Any> toMap(obj: T, onlyPrimaryConstructor: Boolean = false): Map<String, Any> {
        @Suppress("UNCHECKED_CAST")
        val kClass = obj::class as KClass<T>

        val primaryConstructorPropertyNames = onlyPrimaryConstructor.ifThen {
            kClass.primaryConstructor?.parameters?.map { it.name }
        }
        if (primaryConstructorPropertyNames.isNullOrEmpty()) {
            return invokeToMap(obj, kClass, onlyPrimaryConstructor)
        }

        return kClass.memberProperties.associateNotNullStrict { prop ->
            val name = prop.name.takeIf { it in primaryConstructorPropertyNames }
            if (name == null) {
                null to null
            } else {
                name to parseValue(prop, obj, true)
            }
        }
    }

    private fun <T : Any> invokeToMap(
        obj: T,
        kClass: KClass<T>,
        onlyPrimaryConstructor: Boolean
    ): Map<String, Any> {
        return kClass.memberProperties.associateNotNullStrict { prop ->
            prop.name to parseValue(prop, obj, onlyPrimaryConstructor)
        }
    }

    private fun <T : Any> parseValue(
        prop: KProperty1<T, *>,
        obj: T,
        onlyPrimaryConstructor: Boolean
    ): Any? {
        return prop.get(obj)?.let { value -> valueToMap(value, onlyPrimaryConstructor) }
    }

    private fun valueToMap(value: Any, onlyPrimaryConstructor: Boolean = false): Any {
        return when {
            value is Array<*> -> {
                value.mapNotNull { obj -> obj?.let { valueToMap(it, onlyPrimaryConstructor) } }
                    .toTypedArray()
            }
            value is Collection<*> -> {
                if (value is Set<*>) {
                    HashSet<Any>().apply {
                        value.mapNotNull { obj ->
                            obj?.let {
                                valueToMap(
                                    it,
                                    onlyPrimaryConstructor
                                )
                            }
                        }
                    }
                } else {
                    value.mapNotNull { obj -> obj?.let { valueToMap(it, onlyPrimaryConstructor) } }
                }
            }
            value is Map<*, *> -> {
                value.entries.associateNotNullStrict { (key, value) ->
                    key to value?.let { valueToMap(it, onlyPrimaryConstructor) }
                }
            }
            value::class.isData -> {
                toMap(value, onlyPrimaryConstructor)
            }
            else -> value
        }
    }
}